use loco_rs::prelude::*;
use sea_orm::DeleteResult;
use serde::{Deserialize, Serialize};
use uuid::Uuid;

pub use super::_entities::reviews::{self, ActiveModel, Entity, Model};

#[derive(Debug, Deserialize, Serialize)]
pub struct CreateParams {
    pub customer_id: Option<String>,
    pub product_id: Option<String>,
    pub order_item_id: Option<String>,
    pub rating: i32,
    pub content: String,
    pub like: i32,
    pub dislike: i32,
    pub active: bool,
}

#[derive(Debug, Validate, Deserialize)]
pub struct Validator {
    #[validate(range(min = 1, max = 5, message = "Rating must be between 1 and 5."))]
    pub rating: i32,
    #[validate(length(min = 1, message = "Content must not be empty."))]
    pub content: String,
}

impl Validatable for ActiveModel {
    fn validator(&self) -> Box<dyn Validate> {
        Box::new(Validator {
            rating: self.rating.as_ref().to_owned(),
            content: self.content.as_ref().to_owned(),
        })
    }
}

#[async_trait::async_trait]
impl ActiveModelBehavior for super::_entities::reviews::ActiveModel {
    async fn before_save<C>(self, _db: &C, insert: bool) -> Result<Self, DbErr>
    where
        C: ConnectionTrait,
    {
        self.validate()?;
        if insert {
            let mut this = self;
            this.id = ActiveValue::Set(Uuid::new_v4().to_string());
            Ok(this)
        } else {
            Ok(self)
        }
    }
}

impl Model {
    /// finds a review by the provided id
    ///
    /// # Errors
    ///
    /// When could not find review or DB query error
    pub async fn find_by_id(db: &DatabaseConnection, id: &str) -> ModelResult<Self> {
        let review = reviews::Entity::find()
            .filter(
                model::query::condition()
                    .eq(reviews::Column::Id, id)
                    .build(),
            )
            .one(db)
            .await?;
        review.ok_or_else(|| ModelError::EntityNotFound)
    }

    /// finds reviews by the provided customer id
    ///
    /// # Errors
    ///
    /// When could not find reviews or DB query error
    pub async fn find_by_customer_id(
        db: &DatabaseConnection,
        customer_id: &str,
    ) -> ModelResult<Vec<Self>> {
        let reviews = reviews::Entity::find()
            .filter(
                model::query::condition()
                    .eq(reviews::Column::CustomerId, customer_id)
                    .build(),
            )
            .all(db)
            .await?;
        Ok(reviews)
    }

    /// finds reviews by the provided product id
    ///
    /// # Errors
    ///
    /// When could not find reviews or DB query error
    pub async fn find_by_product_id(
        db: &DatabaseConnection,
        product_id: &str,
    ) -> ModelResult<Vec<Self>> {
        let reviews = reviews::Entity::find()
            .filter(
                model::query::condition()
                    .eq(reviews::Column::ProductId, product_id)
                    .build(),
            )
            .all(db)
            .await?;
        Ok(reviews)
    }

    /// Asynchronously creates a review and saves it to the database.
    ///
    /// # Errors
    ///
    /// When could not save the review into the DB
    pub async fn create(db: &DatabaseConnection, params: &CreateParams) -> ModelResult<Self> {
        let review = reviews::ActiveModel {
            customer_id: ActiveValue::set(params.customer_id.clone()),
            product_id: ActiveValue::set(params.product_id.clone()),
            order_item_id: ActiveValue::set(params.order_item_id.clone()),
            rating: ActiveValue::set(params.rating),
            content: ActiveValue::set(params.content.to_string()),
            like: ActiveValue::set(params.like),
            dislike: ActiveValue::set(params.dislike),
            active: ActiveValue::set(params.active),
            ..Default::default()
        }
        .insert(db)
        .await?;

        Ok(review)
    }

    /// Deletes the review
    ///
    /// # Errors
    ///
    /// When could not delete the review from the DB
    pub async fn delete(self, db: &DatabaseConnection) -> ModelResult<DeleteResult> {
        let model: reviews::ActiveModel = self.into();
        Ok(model.delete(db).await?)
    }
}

impl ActiveModel {
    /// Updates the review's active status
    ///
    /// # Errors
    ///
    /// when has DB query error
    pub async fn update_active_status(
        mut self,
        db: &DatabaseConnection,
        active: bool,
    ) -> ModelResult<Model> {
        self.active = ActiveValue::set(active);
        Ok(self.update(db).await?)
    }

    /// Updates the review's like count
    ///
    /// # Errors
    ///
    /// when has DB query error
    pub async fn update_like_count(
        mut self,
        db: &DatabaseConnection,
        like: i32,
    ) -> ModelResult<Model> {
        self.like = ActiveValue::set(like);
        Ok(self.update(db).await?)
    }

    /// Updates the review's dislike count
    ///
    /// # Errors
    ///
    /// when has DB query error
    pub async fn update_dislike_count(
        mut self,
        db: &DatabaseConnection,
        dislike: i32,
    ) -> ModelResult<Model> {
        self.dislike = ActiveValue::set(dislike);
        Ok(self.update(db).await?)
    }
}
